package com.iflytek.springboot_medicine.common;

import com.iflytek.springboot_medicine.pojo.LoginPojo;
import com.iflytek.springboot_medicine.service.impl.ControllerLogServiceImpl;
import com.iflytek.springboot_medicine.service.impl.UserServiceImpl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;

import com.iflytek.springboot_medicine.entity.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;

@Aspect
@Component
@Slf4j
public class ControllerLogAspect {

    // 常量定义
    private static final String[] IP_HEADERS = {
            "X-Forwarded-For",
            "Proxy-Client-IP",
            "WL-Proxy-Client-IP",
            "HTTP_CLIENT_IP",
            "HTTP_X_FORWARDED_FOR"
    };

    private static final String OPERATION_LOGIN = "LOGIN";
    private static final String OPERATION_LOGOUT = "LOGOUT";
    private static final String OPERATION_CREATE = "CREATE";
    private static final String OPERATION_UPDATE = "UPDATE";
    private static final String OPERATION_DELETE = "DELETE";
    private static final String OPERATION_READ = "READ";
    private static final String OPERATION_REGISTER = "REGISTER";
    private static final String OPERATION_OTHER = "OTHER";

    private static final String BEARER_PREFIX = "Bearer ";
    private static final String REDIS_USER_KEY_PREFIX = "USER:";
    private static final long REDIS_EXPIRE_MINUTES = 30;

    // 依赖注入
    private final ControllerLogServiceImpl controllerLogService;
    private final String jwtSecret;
    private final RedisTemplate<String, Object> redisTemplate;
    private final UserServiceImpl userService;

    // 使用 ThreadLocal 存储用户信息
    private static final ThreadLocal<User> loggedInUser = new ThreadLocal<>();

    public ControllerLogAspect(ControllerLogServiceImpl controllerLogService,
                               @Value("${jwt.secret}") String jwtSecret,
                               RedisTemplate<String, Object> redisTemplate,
                               UserServiceImpl userService) {
        this.controllerLogService = controllerLogService;
        this.jwtSecret = jwtSecret;
        this.redisTemplate = redisTemplate;
        this.userService = userService;
    }

    @Pointcut("execution(* com.iflytek.springboot_medicine.controller..*.*(..))")
    public void controllerPointcut() {}

    @Around("controllerPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Objects.requireNonNull(joinPoint, "JoinPoint cannot be null");

        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            log.error("Request attributes are null");
            return joinPoint.proceed();
        }

        HttpServletRequest request = attributes.getRequest();
        if (request == null) {
            log.error("Request is null");
            return joinPoint.proceed();
        }

        logRequestInfo(request);

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        long startTime = System.currentTimeMillis();

        ControllerMethodLog logs = createControllerMethodLog(joinPoint, request, method);
        logs.setIpAddress(getClientIp(request));

        handleSpecialOperations(joinPoint, request, logs);

        processUserInfo(request, logs);

        try {
            Object result = joinPoint.proceed();
            long endTime = System.currentTimeMillis();

            completeLogSuccess(logs, startTime, endTime);
            return result;
        } catch (Exception e) {
            completeLogFailure(logs, startTime, System.currentTimeMillis(), e);
            throw e;
        }
    }
    //处理日志
    private void logRequestInfo(HttpServletRequest request) {
        log.debug("=== Request Information ===");
        log.debug("Request URL: {}", request.getRequestURL());
        log.debug("Request Method: {}", request.getMethod());
        log.debug("Request URI: {}", request.getRequestURI());
        log.debug("Remote Address: {}", request.getRemoteAddr());

        java.util.Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            log.debug("=== Request Headers ===");
            while (headerNames.hasMoreElements()) {
                String headerName = headerNames.nextElement();
                log.debug("Header - {}: {}", headerName, request.getHeader(headerName));
            }
        }
    }

    private ControllerMethodLog createControllerMethodLog(ProceedingJoinPoint joinPoint,
                                                          HttpServletRequest request,
                                                          Method method) {
        ControllerMethodLog logs = new ControllerMethodLog();
        logs.setClassName(joinPoint.getTarget().getClass().getName());
        logs.setMethodName(method.getName());
        logs.setRequestUrl(request.getRequestURI());
        logs.setRequestParams(getRequestParams(joinPoint));
        //处理时间
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        try {
            logs.setCreateTime(sdf.parse(sdf.format(new Date())));
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        logs.setOperationType(determineOperationType(joinPoint));
        return logs;
    }

    //特殊处理登录和登出
    private void handleSpecialOperations(ProceedingJoinPoint joinPoint,
                                         HttpServletRequest request,
                                         ControllerMethodLog logs) {
        if (OPERATION_LOGIN.equals(logs.getOperationType())) {
            handleLoginRequest(joinPoint, logs);
        } else if (OPERATION_LOGOUT.equals(logs.getOperationType())) {
            extractUserInfoFromToken(request, logs);
            User user = loggedInUser.get();
            if (user != null) {
                setUserInfoToLogs(logs, user);
                log.debug("Logout detected for user: id={}, username={}", user.getId(), user.getName());
            }
        }
    }

    //处理用户信息，尝试从请求中提取用户信息
    private void processUserInfo(HttpServletRequest request, ControllerMethodLog logs) {
        User user = loggedInUser.get();
        if (user == null && !OPERATION_LOGIN.equals(logs.getOperationType())
                && !OPERATION_LOGOUT.equals(logs.getOperationType())) {
            extractUserInfoFromToken(request, logs);
            user = loggedInUser.get();
        }

        if (user != null) {
            setUserInfoToLogs(logs, user);
        }
    }

    //记录成功日志
    private void completeLogSuccess(ControllerMethodLog logs, long startTime, long endTime) {
        logs.setExecutionTime(endTime - startTime);
        logs.setStatus(1);
        controllerLogService.saveLog(logs);

        if (OPERATION_LOGOUT.equals(logs.getOperationType())) {
            loggedInUser.remove();
        }
    }
    //记录失败日志
    private void completeLogFailure(ControllerMethodLog logs, long startTime, long endTime, Exception e) {
        logs.setExecutionTime(endTime - startTime);
        logs.setStatus(0);
        logs.setErrorMessage(e.getMessage());
        controllerLogService.saveLog(logs);
    }

    private void setUserInfoToLogs(ControllerMethodLog logs, User user) {
        logs.setUserId(user.getId());
        logs.setUsername(user.getName());
        logs.setUserRole(user.getRole());
    }
    //登录时尝试从请求参数中获取用户名并查找用户信息，登出时提取用户信息并记录日志
    private void handleLoginRequest(ProceedingJoinPoint joinPoint, ControllerMethodLog logs) {
        Arrays.stream(joinPoint.getArgs())
                .filter(arg -> arg instanceof LoginPojo)
                .findFirst()
                .ifPresent(loginArg -> {
                    LoginPojo loginPojo = (LoginPojo) loginArg;
                    User user = userService.findUserByUsername(loginPojo.getUsername());
                    if (user != null) {
                        setUserInfoToLogs(logs, user);
                        log.debug("Login attempt detected for user: id={}, username={}",
                                user.getId(), user.getName());
                        loggedInUser.set(user);
                    }
                });
    }
    //从请求中提取 JWT，尝试从 Redis 中获取用户信息，若不存在则解析 JWT 并从数据库中查找用户信息，最后将用户信息存储到 ThreadLocal 和 Redis 中
    private void extractUserInfoFromToken(HttpServletRequest request, ControllerMethodLog logs) {
        resolveToken(request).ifPresent(token -> {
            try {
                getUserFromRedis(token).ifPresentOrElse(
                        user -> setUserAndCache(logs, user, token),
                        () -> parseToken(token).ifPresent(claims ->
                                processClaims(claims, token, logs))
                );
            } catch (Exception e) {
                log.error("Token processing failed", e);
            }
        });
    }

    //从请求头中解析出 JWT
    private Optional<String> resolveToken(HttpServletRequest request) {
        return Optional.ofNullable(request.getHeader("Authorization"))
                .filter(authHeader -> authHeader.startsWith(BEARER_PREFIX))
                .map(authHeader -> authHeader.substring(BEARER_PREFIX.length()));
    }

    //从 Redis 中获取用户信息
    private Optional<User> getUserFromRedis(String token) {
        return Optional.ofNullable(
                (User) redisTemplate.opsForValue().get(REDIS_USER_KEY_PREFIX + token));
    }

    //解析JWT，获取其中的声明信息
    private Optional<Claims> parseToken(String token) {
        try {
            return Optional.of(Jwts.parser()
                    .setSigningKey(jwtSecret)
                    .parseClaimsJws(token)
                    .getBody());
        } catch (Exception e) {
            log.error("Token parsing failed", e);
            return Optional.empty();
        }
    }

    //处理 JWT 中的声明信息，根据用户名查找用户信息，并将用户信息存储到 ThreadLocal 和 Redis 中
    private void processClaims(Claims claims, String token, ControllerMethodLog logs) {
        Optional.ofNullable((String) claims.get("username"))
                .flatMap(username -> Optional.ofNullable(userService.findByName(username)))
                .ifPresent(user -> {
                    setUserAndCache(logs, user, token);
                    log.debug("User info set from JWT: userId={}, username={}",
                            user.getId(), user.getName());
                });
    }

    //将用户信息设置到 ControllerMethodLog 对象中，存储到 ThreadLocal 中，并缓存到 Redis 中
    private void setUserAndCache(ControllerMethodLog logs, User user, String token) {
        setUserInfoToLogs(logs, user);
        loggedInUser.set(user);
        cacheUserInfo(token, user);
    }

    private void cacheUserInfo(String token, User user) {
        redisTemplate.opsForValue().set(
                REDIS_USER_KEY_PREFIX + token,
                user,
                REDIS_EXPIRE_MINUTES,
                TimeUnit.MINUTES
        );
        log.debug("Stored user info in Redis with key: {}", REDIS_USER_KEY_PREFIX + token);
    }

    //获取请求参数，排除 HttpServletRequest 和 HttpServletResponse 对象
    private String getRequestParams(ProceedingJoinPoint joinPoint) {
        StringBuilder params = new StringBuilder();
        Arrays.stream(joinPoint.getArgs())
                .filter(arg -> arg != null && !(arg instanceof HttpServletRequest)
                        && !(arg instanceof HttpServletResponse))
                .forEach(arg -> params.append(arg.toString()).append("; "));
        return params.toString();
    }

    //根据方法名确定操作类型
    private String determineOperationType(ProceedingJoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName().toLowerCase();

        if (methodName.contains("login")) {
            return OPERATION_LOGIN;
        } else if (methodName.contains("logout") || methodName.contains("unlogin")) {
            return OPERATION_LOGOUT;
        } else if (methodName.contains("register")) {
            return OPERATION_REGISTER;
        } else if (methodName.contains("add") || methodName.contains("create")
                || methodName.contains("insert")) {
            return OPERATION_CREATE;
        } else if (methodName.contains("update") || methodName.contains("modify")) {
            return OPERATION_UPDATE;
        } else if (methodName.contains("delete") || methodName.contains("remove")) {
            return OPERATION_DELETE;
        } else if (methodName.contains("get") || methodName.contains("find")
                || methodName.contains("query")) {
            return OPERATION_READ;
        }
        return OPERATION_OTHER;
    }

    //获取客户端的真实 IP 地址，优先从请求头中获取，若不存在则使用 getRemoteAddr 方法
    private String getClientIp(HttpServletRequest request) {
        return Arrays.stream(IP_HEADERS)
                .map(request::getHeader)
                .filter(ip -> StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip))
                .findFirst()
                .map(ip -> ip.split(",")[0].trim())
                .orElseGet(request::getRemoteAddr);
    }
}